﻿//TODO: ConnectedComponents: this has to be thoroughly tested

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlTypes;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Summary description for ConnectedComponents.
    /// </summary>
    public class ConnectedComponents : BaseImageFilter
    {
        public static int SAMPLES;
        private BitmapData data;
        private int stride;
        private IntPtr ptr;
        protected int m_nThreshold = 220;

        private int nOffset;
        public int line = 0;
        private int startX, finalX;
        private int startY = int.MaxValue;
        private int finalY = int.MinValue;
        public string[][] urdu;

        public ObjectExtractorObject objects = new ObjectExtractorObject();
        /** Option for finding 4-connected components. */
        public readonly static int N4 = 1;
        /** Option for finding D-connected components.*/
        public readonly static int ND = 2;

        /** Option for finding 8-connected components. */
        public readonly static int N8 = 3;

        private Bitmap image;
        private Bitmap IntensityImage;   //Gray Scale Image
        public Bitmap BinaryImage;
        private int numRows;
        private int numCols;
        private int m_nLabel;
        private int nbhood;
        public static System.IO.StreamWriter finalFile;

        /// <summary>
        /// 
        /// </summary>
        public ConnectedComponents()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectedComponents"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        public ConnectedComponents(Bitmap _bitmap)
            : this(_bitmap, N4)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectedComponents"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_nNeighbourhood">The _n neighbourhood.</param>
        public ConnectedComponents(Bitmap _bitmap, int _nNeighbourhood)
        {
            this.image = _bitmap;
            IntensityImage = (Bitmap)_bitmap.Clone();
            this.GrayScale(IntensityImage);
            Binary(IntensityImage, true);
            BinaryImage = (Bitmap)_bitmap.Clone();
            Binary(BinaryImage, true);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectedComponents"/> class.
        /// </summary>
        /// <param name="_connectedComponents">The _connected components.</param>
        internal ConnectedComponents(ConnectedComponents _connectedComponents)
        {
            this.image = _connectedComponents.image;
            this.IntensityImage = _connectedComponents.IntensityImage;
            this.nbhood = _connectedComponents.nbhood;
            this.m_nThreshold = _connectedComponents.m_nThreshold;
        }

        
        /// <summary>
        /// Gets or sets the threshold.
        /// </summary>
        /// <value>The threshold.</value>
        public int Threshold
        {
            get { return m_nThreshold; }
            set { m_nThreshold = value; }
        }

        /// <summary>
        /// Locks the image.
        /// </summary>
        protected void LockImage()
        {
            data = IntensityImage.LockBits(new Rectangle(0, 0, this.numCols, this.numRows), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            stride = data.Stride;
            ptr = data.Scan0;
            nOffset = stride - IntensityImage.Width * 3;
            m_nLabel = 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            _bitmap.UnlockImage();            
            UnsafeBitmap bitmap = new UnsafeBitmap(_bitmap.InternalBitmap, PixelFormat.Format24bppRgb);
            ObjectIsolation(bitmap);
            return _bitmap;
        }

        /// <summary>
        /// Returns the labeled image in which the value of each pixel is
        /// the label of its connected component. All background pixels have
        /// value zero.
        /// </summary>
        /// <returns>The labeled image</returns>
        public UnsafeBitmap GetIntensityImage()
        {
            //IntensityImage.Save(@"c:/");
            return IntensityImage;
        }

        /// <summary>
        /// Gets the image.
        /// </summary>
        /// <returns></returns>
        public UnsafeBitmap GetImage()
        {
            //IntensityImage.Save(@"c:/");
            return image;
        }

        /// <summary>
        /// Returns the value of the label of the last connected component
        /// found in the image. This is the same as the number of connected
        /// components of the image.
        /// The label of the last connected component
        /// </summary>
        public int Label
        {
            get { return m_nLabel; }
        }

        /// <summary>
        /// Isolates the objects.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        public void ObjectIsolation(UnsafeBitmap _bitmap)
        {
            this.nbhood = nbhood;
            this.numRows = _bitmap.Height;
            this.numCols = _bitmap.Width;

            //not
            //data = _bitmap.LockBits(new Rectangle(0, 0, this.numCols, this.numRows), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            data = _bitmap.BitmapData;
            stride = data.Stride;
            ptr = data.Scan0;

            int nBytesPerPixelCount = 3;//_bitmap.BytesPerPixelCount;
            nOffset = stride - _bitmap.Width * nBytesPerPixelCount;

            unsafe
            {
                byte* p;

                p = (byte*)(void*)ptr;
                //p+=stride;
                for (int y = 1; y < numRows - 1; y++)
                {
                    //p+=3;											
                    for (int x = 1; x < numCols - 3; x++)
                    {
                        if ((p + x * 3 + stride * y)[0] == 0)
                        {
                            m_nLabel++;
                            try
                            {
                                startY = 10000;
                                finalY = 0;
                                startX = int.MaxValue;
                                finalX = 0;
                                Search(m_nLabel, y, x);
                                LowLevelGraphics.ObjectExtractorObject singleObject = new LowLevelGraphics.ObjectExtractorObject();
                                singleObject.StartX = startX;
                                singleObject.StartY = this.startY;
                                singleObject.FinalX = finalX;
                                singleObject.FinalY = finalY;
                                singleObject.Label = m_nLabel;
                                objects.Add(singleObject);
                            }
                            catch (StackOverflowException e)
                            {
                                MessageBox.Show(e.Message.ToString());
                                return;
                            }
                        }
                        //p+=3;						
                    }
                    //p+=stride;									
                }
                int t = 0;
                t++;
            }
            _bitmap.UnlockImage();
        }

        /// <summary>
        /// Searches the specified label.
        /// </summary>
        /// <param name="Label">The label.</param>
        /// <param name="r">The r.</param>
        /// <param name="c">The c.</param>
        protected void Search(int Label, int r, int c)
        {
            if (r < startY)
                startY = r;

            if (r > finalY)
                finalY = r;
            if (c > finalX)
                finalX = c;
            if (c < startX)
                startX = c;

            try
            {
                int[] nb = { r, c };
                ArrayList nbList = FindNeighbours(nb);
                unsafe
                {
                    byte* p = (byte*)(void*)ptr;
                    (p + r * stride + c * 3)[0] = (byte)Label;
                    (p + r * stride + c * 3)[1] = (byte)Label;
                    (p + r * stride + c * 3)[2] = (byte)(Label * 20);

                    int i = 0;
                    for (i = 0; i < nbList.Count; i++)
                    {
                        int[] pos = (int[])nbList[i];
                        if ((p + pos[0] * stride + pos[1] * 3)[0] == 0)
                            Search(Label, pos[0], pos[1]);
                    }
                }
            }
            catch (StackOverflowException e)
            {
                MessageBox.Show(e.Message.ToString());
                return;
            }
        }

        /// <summary>
        /// Finds the neighbours.
        /// </summary>
        /// <param name="pos">The pos.</param>
        /// <returns></returns>
        private ArrayList FindNeighbours(int[] pos)
        {
            ArrayList nbList;

            if (nbhood == ND)
                nbList = FindDNeighbours(pos);
            else if (nbhood == N8)
                nbList = find8Neighbours(pos);
            else
                nbList = Find4Neighbours(pos);

            return nbList;
        }

        /// <summary>
        /// Find4s the neighbours.
        /// </summary>
        /// <param name="pos">The pos.</param>
        /// <returns></returns>
        protected ArrayList Find4Neighbours(int[] pos)
        {
            ArrayList nbList = new ArrayList();

            if (pos[0] > 0)
                AddNeighbour(pos[0] - 1, pos[1], nbList);
            if (pos[1] > 0)
                AddNeighbour(pos[0], pos[1] - 1, nbList);
            if (pos[1] < numCols - 1)
                AddNeighbour(pos[0], pos[1] + 1, nbList);
            if (pos[0] < numRows - 1)
                AddNeighbour(pos[0] + 1, pos[1], nbList);

            return nbList;
        }

        /// <summary>
        /// Finds the D neighbours.
        /// </summary>
        /// <param name="pos">The pos.</param>
        /// <returns></returns>
        protected ArrayList FindDNeighbours(int[] pos)
        {
            ArrayList nbList = new ArrayList();

            if ((pos[0] > 0) && (pos[1] > 0))
                AddNeighbour(pos[0] - 1, pos[1] - 1, nbList);
            if ((pos[0] > 0) && (pos[1] < numCols - 1))
                AddNeighbour(pos[0] - 1, pos[1] + 1, nbList);
            if ((pos[0] < numRows - 1) && (pos[1] > 0))
                AddNeighbour(pos[0] + 1, pos[1] - 1, nbList);
            if ((pos[0] < numRows - 1) && (pos[1] < numCols - 1))
                AddNeighbour(pos[0] + 1, pos[1] + 1, nbList);

            return nbList;
        }

        /// <summary>
        /// Find8s the neighbours.
        /// </summary>
        /// <param name="pos">The pos.</param>
        /// <returns></returns>
        private ArrayList find8Neighbours(int[] pos)
        {
            ArrayList nbList = new ArrayList();

            if ((pos[0] > 0) && (pos[1] > 0))
                AddNeighbour(pos[0] - 1, pos[1] - 1, nbList);
            if (pos[0] > 0)
                AddNeighbour(pos[0] - 1, pos[1], nbList);
            if ((pos[0] > 0) && (pos[1] < numCols - 1))
                AddNeighbour(pos[0] - 1, pos[1] + 1, nbList);
            if (pos[1] > 0)
                AddNeighbour(pos[0], pos[1] - 1, nbList);
            if (pos[1] < numCols - 1)
                AddNeighbour(pos[0], pos[1] + 1, nbList);
            if ((pos[0] < numRows - 1) && (pos[1] > 0))
                AddNeighbour(pos[0] + 1, pos[1] - 1, nbList);
            if (pos[0] < numRows - 1)
                AddNeighbour(pos[0] + 1, pos[1], nbList);
            if ((pos[0] < numRows - 1) && (pos[1] < numCols - 1))
                AddNeighbour(pos[0] + 1, pos[1] + 1, nbList);

            return nbList;
        }

        /// <summary>
        /// Adds the neighbour.
        /// </summary>
        /// <param name="r">The r.</param>
        /// <param name="c">The c.</param>
        /// <param name="list">The list.</param>
        private void AddNeighbour(int r, int c, ArrayList list)
        {
            int[] nb = { r, c };
            list.Add(nb);
        }

        /// <summary>
        /// Grays the scale.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <returns></returns>
        public bool GrayScale(Bitmap _bitmap)
        {
            // GDI+ still lies to us - the return format is BGR, NOT RGB. 
            BitmapData bmData = _bitmap.LockBits(new Rectangle(0, 0, _bitmap.Width, _bitmap.Height),
                ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            int stride = bmData.Stride; //the length of the line
            IntPtr Scan0 = bmData.Scan0;

            unsafe
            {
                byte* p = (byte*)(void*)Scan0;

                int nOffset = stride - _bitmap.Width * 3;

                byte red, green, blue;

                for (int y = 0; y < _bitmap.Height; ++y)
                {
                    for (int x = 0; x < _bitmap.Width; ++x)
                    {
                        blue = p[0];
                        green = p[1];
                        red = p[2];

                        p[0] = p[1] = p[2] = (byte)(.299 * red
                            + .587 * green
                            + .114 * blue);

                        p += 3;
                    }
                    p += nOffset;
                }
            }

            _bitmap.UnlockBits(bmData);

            return true;
        }

        /// <summary>
        /// Binarizes the specified b.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="flag">if set to <c>true</c> [flag].</param>
        /// <returns></returns>
        public bool Binary(Bitmap _bitmap, bool flag)
        {
            // GDI+ still lies to us - the return format is BGR, NOT RGB. 
            BitmapData bmData = _bitmap.LockBits(new Rectangle(0, 0, _bitmap.Width, _bitmap.Height),
                ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            int stride = bmData.Stride; //the length of the line
            IntPtr Scan0 = bmData.Scan0;
            unsafe
            {
                byte* p = (byte*)(void*)Scan0;

                int nOffset = stride - _bitmap.Width * 3;

                byte red, green, blue;
                byte binary;

                for (int y = 0; y < _bitmap.Height; ++y)
                {
                    for (int x = 0; x < _bitmap.Width; ++x)
                    {
                        blue = p[0];
                        green = p[1];
                        red = p[2];

                        binary = (byte)(.299 * red
                            + .587 * green
                            + .114 * blue);

                        if (binary < m_nThreshold && flag)
                        {
                            p[0] = p[1] = p[2] = 0;
                        }
                        else
                        {
                            if (binary >= m_nThreshold && flag)
                            {
                                p[0] = p[1] = p[2] = 255;
                            }
                            else
                            {
                                if (binary < m_nThreshold && !flag)
                                {
                                    p[0] = p[1] = p[2] = 255;
                                }
                                else
                                {
                                    p[0] = p[1] = p[2] = 0;
                                }
                            }
                        }
                        p += 3;
                    }
                    p += nOffset;
                }
            }
            _bitmap.UnlockBits(bmData);
            return true;
        }

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new ConnectedComponents(this);
        }
    }
}